///////////////////////////////////////////////////////////////
// Tests for Value class
//

#include <gtest/gtest.h>

#include "../value.h"

#include "../ast.h"	// for Label
#include "../string.h"
#include "../table.h"
#include "../function.h"



// A mock-object template that sets a flag on destruction;
// used to make sure that reference-counted values are
// deleted at the right times.
template<typename T> class DeleteMock : public T
{
private:
	bool& deleted;
public:
	DeleteMock(bool &b) : deleted(b) { }
	~DeleteMock() { deleted = true; }
};

// Individual deletion mocks
typedef DeleteMock<Table> MockTable;
typedef DeleteMock<String> MockString;
typedef DeleteMock<Function> MockFunction;



//
// Tests that values created from numbers have expected properties:
//  - Should have number type
//  - Should not be reference counted
//  - Should not be a reference
//
TEST(ValueTest, NumberValue)
{
	Value value(123);

	EXPECT_EQ(Type::Number, (Type::EType)value.GetType());
	EXPECT_FALSE(value.IsRefCounted());
	EXPECT_FALSE(value.IsValidRef());
}

//
// Tests that values created from labels have expected properties
//
TEST(ValueTest, LabelValue)
{
	// We don't need an actual Label object here
	Label* label = NULL;
	Value value(label);

	EXPECT_EQ(Type::Label, (Type::EType)value.GetType());
	EXPECT_FALSE(value.IsRefCounted());
	EXPECT_FALSE(value.IsValidRef());
}

//
// Tests that values created from strings have expected properties
//  - Should have string type
//  - Should be reference-counted, and valid references
//
TEST(ValueTest, StringValue)
{
	String* str = new String();
	Value value(str);

	EXPECT_EQ(Type::String, (Type::EType)value.GetType());
	EXPECT_TRUE(value.IsRefCounted());
	EXPECT_TRUE(value.IsValidRef());
}

//
// Tests that values created from tables have expected properties
//
TEST(ValueTest, TableValue)
{
	Table* tbl = new Table();
	Value value(tbl);

	EXPECT_EQ(Type::Table, (Type::EType)value.GetType());
	EXPECT_TRUE(value.IsRefCounted());
	EXPECT_TRUE(value.IsValidRef());
}

//
// Tests that values created from functions have expected properties
//
TEST(ValueTest, FunctionValue)
{
	Function* fn = new Function();
	Value value(fn);

	EXPECT_EQ(Type::Function, (Type::EType)value.GetType());
	EXPECT_TRUE(value.IsRefCounted());
	EXPECT_TRUE(value.IsValidRef());
}


//
// Tests that the assignment operator correctly assigns values
//
TEST(ValueTest, Assignment)
{
	Value value;

	// Assign to it an integer Value
	Value other(12345);
	value = other;
	EXPECT_EQ(value.GetType(), other.GetType());
	EXPECT_EQ(value.GetNumber(), other.GetNumber());
	EXPECT_FALSE(value.IsRefCounted());

	// Assign a string value
	Value string(new String());
	value = string;
	EXPECT_EQ(value.GetType(), string.GetType());
	EXPECT_EQ(value.GetWeakString(), string.GetWeakString());
	EXPECT_TRUE(value.IsRefCounted());

	// Assign another number to make sure IsRefCount gets reset :)
	value = 123;
	EXPECT_FALSE(value.IsRefCounted());

	// Assign a table value
	Value table(new Table());
	value = table;
	EXPECT_EQ(value.GetType(), table.GetType());
	EXPECT_EQ(value.GetWeakTable(), table.GetWeakTable());
	EXPECT_TRUE(value.IsRefCounted());

	// Assign a function value
	Value function(new Function());
	value = function;
	EXPECT_EQ(value.GetType(), function.GetType());
	EXPECT_EQ(value.GetWeakFunction(), function.GetWeakFunction());
	EXPECT_TRUE(value.IsRefCounted());

	// Assign a label value
	Value label((Label*)NULL);
	value = label;
	EXPECT_EQ(value.GetType(), label.GetType());
	EXPECT_EQ(value.GetLabel(), label.GetLabel());
	EXPECT_FALSE(value.IsRefCounted());
}


//
// Tests that references are appropriately released on assignment
//
TEST(ValueTest, AssignmentRef)
{
	bool deleted = false;
	MockTable* table = new MockTable(deleted);

	Value value(table);

	EXPECT_FALSE(deleted) << "object deleted too soon!";
	EXPECT_TRUE(value.IsValidRef());

	// Assign an integer value
	value = 123;

	EXPECT_TRUE(deleted) << "reference was not released on assignment!";
	EXPECT_FALSE(value.IsValidRef());
}


//
// Tests that copy construction produces equivalent values
//
TEST(ValueTest, CopyConstruction)
{
	Value number(12345);

	Value value1(number);
	EXPECT_EQ(value1.GetType(), number.GetType());
	EXPECT_EQ(value1.GetNumber(), number.GetNumber());

	Value string(new String());

	Value value2(string);
	EXPECT_EQ(value2.GetType(), string.GetType());
	EXPECT_EQ(value2.GetWeakString(), string.GetWeakString());
	EXPECT_TRUE(value2.IsRefCounted());
	EXPECT_TRUE(value2.IsValidRef());
}



//
// Tests basic reference-counting behavior
//
TEST(ValueTest, ReferenceCounting)
{
	bool deleted = false;
	MockTable* foo = new MockTable(deleted);

	// Create an initial Table reference
	Value v1(foo);

	{
		Value v2(v1);

		EXPECT_EQ(v1.GetWeakTable(), v2.GetWeakTable()) << "references not equal!";
		ASSERT_TRUE(v2.IsRefCounted());
		ASSERT_TRUE(v2.IsValidRef());
		ASSERT_FALSE(deleted) << "object deleted too soon!";

		v1.Release();
		ASSERT_FALSE(v1.IsValidRef());
		ASSERT_FALSE(deleted) << "object deleted too soon!";

		Value v3 = v2;
		ASSERT_FALSE(deleted) << "object deleted too soon!";

		// When this block scope ends, the last two references
		// should be released.
	}

	EXPECT_TRUE(deleted) << "object not deleted when all references released!";
}